﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using CC.Utilities;

namespace CC.Yacht.Server
{
    public class ExtendedGameState : IComparable<Game>, IEquatable<Game>, IComparable<GameState>, IEquatable<GameState>, IComparable<ExtendedGameState>, IEquatable<ExtendedGameState>
    {
        #region Constructor
        public ExtendedGameState(ExtendedUser owner, Guid lobbyId)
        {
            Dice = new Dice(new[] {new Die(), new Die(), new Die(), new Die(), new Die()});
            GameId = Guid.NewGuid();
            LobbyId = lobbyId;
            Players = new List<ExtendedUser>();
            Owner = owner;
            Scorecards = new Dictionary<long, Scorecard>();
            
            Join(owner);
        }
        #endregion

        #region Private Fields
        private readonly object _LockObject = new object();
        #endregion

        #region Public Properties
        public ExtendedUser CurrentUser { get; set; }

        public Dice Dice { get; set; }

        public Guid GameId { get; set; }

        public bool IsStarted { get; set; }

        public Guid LobbyId { get; set; }

        public ExtendedUser Owner { get; set; }

        public List<ExtendedUser> Players { get; set; }

        public Dictionary<long, Scorecard> Scorecards { get; set; }
        #endregion

        #region Private Methods
        private void AdvanceCurrentUser()
        {
            int index = 0;

            if (!IsGameOver())
            {
                if (CurrentUser != null)
                {
                    index = Players.IndexOf(CurrentUser) + 1;
                    if (index > Players.Count - 1)
                    {
                        index = 0;
                    }
                }

                CurrentUser = Players[index];

                while (Scorecards[CurrentUser.UserId].IsGameOver()) // This while fixes the "extra" turns a user gets when they score 2 or more yachts
                {
                    index = Players.IndexOf(CurrentUser) + 1;
                    if (index > Players.Count - 1)
                    {
                        index = 0;
                    }

                    CurrentUser = Players[index];
                }

                Dice.Reset();
            }
            else
            {
                OnGameOver(Scorecards.Values.OrderByDescending(s => s.Total).First().User);
                Dice.RollCount = 3;
                IsStarted = false;
            }
        }

        private bool HasPermission(ExtendedUser user, bool checkCurrentUser, bool checkOwner)
        {
            bool returnValue = true;

            if (!Players.Contains(user))
            {
                returnValue = false;
            }

            if (checkCurrentUser && !user.Equals(CurrentUser))
            {
                returnValue = false;
            }

            if (checkOwner && !user.Equals(Owner))
            {
                returnValue = false;
            }

            return returnValue;
        }

        /// <summary>
        /// Joins a user to the game. Should only be used with an <see cref="ExtendedUser"/> from <see cref="SessionManager"/>
        /// </summary>
        /// <param name="extendedUser">The <see cref="ExtendedUser"/> joining the game.</param>
        /// <returns>true if successfully joined; false if not</returns>
        // ReSharper disable SuggestBaseTypeForParameter
        private bool InternalJoin(ExtendedUser extendedUser)
        // ReSharper restore SuggestBaseTypeForParameter
        {
            bool returnValue = false;

            lock (_LockObject)
            {
                if (Players.Contains(extendedUser))
                {
                    Players[Players.IndexOf(extendedUser)] = extendedUser;

                    returnValue = true;
                }
                else if (Players.Count < Constants.MaximumPlayers)
                {
                    Players.Add(extendedUser);
                    Scorecards.Add(extendedUser.UserId, new Scorecard(extendedUser.ToUser()));
                    OnStateChanged();

                    returnValue = true;
                }
            }

            return returnValue;
        }

        private bool InternalLeave(ExtendedUser extendedUser)
        {
            bool returnValue = false;

            lock (_LockObject)
            {
                if (Players.Contains(extendedUser))
                {
                    Players.Remove(extendedUser);
                    Scorecards.Remove(extendedUser.UserId);

                    if (extendedUser.Equals(Owner) && Players.Count > 0)
                    {
                        Owner = Players[0];
                    }

                    OnStateChanged();

                    returnValue = true;
                }
            }

            return returnValue;
        }

        private bool IsGameOver()
        {
            bool returnValue = true;

            foreach (Scorecard scorecard in Scorecards.Values)
            {
                if (!scorecard.IsGameOver())
                {
                    returnValue = false;
                    break;
                }
            }

            return returnValue;
        }

        private void OnStateChanged()
        {
            for (int i = Players.Count - 1; i >= 0; i--)
            {
                ExtendedUser tempUser = Players[i];

                if (tempUser != null && tempUser.Callback != null)
                {
                    try
                    {
                        tempUser.Callback.GameStateChanged(ToGameState());
                    }
                    catch (CommunicationException exception)
                    {
                        tempUser.Callback = null;
                        Logging.LogException(exception);
                    }
                }
            }
        }

        private void OnRoll()
        {
            foreach (var tempUser in Players)
            {
                if (tempUser.Callback != null)
                {
                    try
                    {
                        tempUser.Callback.GameRolled(Dice);
                    }
                    catch (CommunicationException exception)
                    {
                        tempUser.Callback = null;
                        Logging.LogException(exception);
                    }
                }
            }            
        }

        private void OnGameOver(User user)
        {
            foreach (var tempUser in Players)
            {
                if (tempUser.Callback != null)
                {
                    try
                    {
                        tempUser.Callback.GameOver(user);
                    }
                    catch (CommunicationException exception)
                    {
                        tempUser.Callback = null;
                        Logging.LogException(exception);
                    }
                }
            }
        }

        private void OnSendGameMessage(ExtendedUser user, string message)
        {
            if (!string.IsNullOrEmpty(message))
            {
                foreach (var tempUser in Players)
                {
                    if (tempUser.Callback != null)
                    {
                        try
                        {
                            tempUser.Callback.GameMessageReceived(user.ToUser(), message);
                        }
                        catch (CommunicationException exception)
                        {
                            tempUser.Callback = null;
                            Logging.LogException(exception);
                        }
                    }
                }
            }
        }
        #endregion

        #region Public Methods
        public List<User> GetPlayers()
        {
            return Players.Select(extendedUser => extendedUser.ToUser()).ToList();
        }

        public bool Join(ExtendedUser user)
        {
            return InternalJoin(user);
        }

        public bool Leave(ExtendedUser user)
        {
            return InternalLeave(user);
        }

        public bool Roll(ExtendedUser user)
        {
            bool returnValue = false;

            if (HasPermission(user, true, false))
            {
                Dice.Roll();
                OnRoll();
                OnStateChanged();
                returnValue = true;
            }

            return returnValue;
        }

        public bool SendMessage(ExtendedUser user, string message)
        {
            bool returnValue = false;

            if (HasPermission(user, false, false))
            {
                OnSendGameMessage(user, message);
                returnValue = true;
            }

            return returnValue;
        }

        public bool SetScoreField(ExtendedUser user, ScoreField scoreField)
        {
            bool returnValue = false;

            if (HasPermission(user, true, false))
            {
                Scorecards[user.UserId].SetScoreField(scoreField, Dice);
                AdvanceCurrentUser();
                OnStateChanged();

                returnValue = true;
            }

            return returnValue;
        }

        public bool Start(ExtendedUser user)
        {
            bool returnValue = false;

            if (!IsStarted && HasPermission(user, false, true))
            {
                foreach (KeyValuePair<long, Scorecard> keyValuePair in Scorecards)
                {
                    keyValuePair.Value.Reset();
                }

                AdvanceCurrentUser();
                IsStarted = true;
                OnStateChanged();

                returnValue = true;
            }

            return returnValue;
        }

        public Game ToGame()
        {
            return new Game(GameId)
                       {
                           IsStarted = IsStarted,
                           Owner = (Owner != null) ? Owner.ToUser() : null,
                           Players = GetPlayers(),
                       };
        }

        public GameState ToGameState()
        {
            return new GameState
                       {
                           CurrentUser = (CurrentUser != null) ? CurrentUser.ToUser() : null,
                           Dice = Dice,
                           GameId = GameId,
                           IsStarted = IsStarted, 
                           Owner = (Owner != null) ? Owner.ToUser() : null, 
                           Players = GetPlayers(), 
                           Scorecards = Scorecards
                       };
        }

        public bool UpdateDice(ExtendedUser user, Dice dice)
        {
            bool returnValue = false;

            if (HasPermission(user, true, false))
            {
                Dice.Die1.Locked = dice.Die1.Locked;
                Dice.Die2.Locked = dice.Die2.Locked;
                Dice.Die3.Locked = dice.Die3.Locked;
                Dice.Die4.Locked = dice.Die4.Locked;
                Dice.Die5.Locked = dice.Die5.Locked;
                
                OnStateChanged();

                returnValue = true;
            }

            return returnValue;
        }
        #endregion

        #region IComparable
        public int CompareTo(ExtendedGameState other)
        {
            int returnValue = -1;

            if (other != null)
            {
                returnValue = GameId.CompareTo(other.GameId);
            }

            return returnValue;
        }

        public int CompareTo(Game other)
        {
            int returnValue = -1;

            if (other != null)
            {
                returnValue = GameId.CompareTo(other.GameId);
            }

            return returnValue;
        }

        public int CompareTo(GameState other)
        {
            int returnValue = -1;

            if (other != null)
            {
                returnValue = GameId.CompareTo(other.GameId);
            }

            return returnValue;
        }
        #endregion

        #region IEquatable
        public bool Equals(ExtendedGameState other)
        {
            bool returnValue = false;

            if (other != null)
            {
                returnValue = (GameId == other.GameId);
            }

            return returnValue;
        }

        public bool Equals(Game other)
        {
            bool returnValue = false;

            if (other != null)
            {
                returnValue = (GameId == other.GameId);
            }

            return returnValue;
        }

        public bool Equals(GameState other)
        {
            bool returnValue = false;

            if (other != null)
            {
                returnValue = (GameId == other.GameId);
            }

            return returnValue;
        }
        #endregion
    }
}
